package com.example.practiceopenglforyuv;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.util.Log;
import android.util.Size;
import android.view.Surface;

import java.util.Arrays;
import java.util.concurrent.Semaphore;

import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;

/**
 * name : Camera2 API
 * time : 2019.11.6
 */

class MCamera2 {
    /**
     * Global Variable
     */
    // for log
    private static final String tag = "_" + "MCamera2";
    // for background thread
    private HandlerThread mBackgroundThread;
    private Handler mBackgroundHandler;
    // for camera
    private String mCameraId;
    private CameraManager mCameraManager;
    private CameraDevice mCameraDevice;
    private CameraCaptureSession mCaptureSession;
    // for the number of thread
    private Semaphore mCameraOpenCloseLock = new Semaphore(1);
    // for parameters
    private StreamConfigurationMap mStreamConfiguration;
    // for capture image
    private Size mCapSize;
    private int mCapFormat;
    private ImageReader mCapReader;
    private Surface mCapSurface;
    // for preview image
    private Size mPreSize;
    private int mPreFormat;
    private ImageReader mPreReader;
    private Surface mPreSurface;
    // for environment
    private Context mContext;
    private MGLRenderer mGLRenderer;

    /**
     * 构造函数，进行一些参数的初始化，以及设置环境参数
     *
     * @param context       活动对象
     * @param glRenderer    预览渲染器对象
     */
    MCamera2(Context context, MGLRenderer glRenderer) {
        // config camera2
        mContext = context;
        mGLRenderer = glRenderer;
        mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);

        mCameraId = "0";
        mCapSize = new Size(1920, 1080);
        mCapFormat = ImageFormat.YV12;
        mPreSize = new Size(1280, 720);
        mPreFormat = ImageFormat.YV12;

        mStreamConfiguration = null;

        mBackgroundThread = null;
        mBackgroundHandler = null;
        startBackgroundThread();
    }

    /**
     * 释放相机相关资源
     */
    void unMCamera2() {
        // destroy camera2
        stopPreview();
        if (mCapReader != null) {
            mCapReader.close();
            mCapReader = null;
        }
        if (mPreReader != null) {
            mPreReader.close();
            mPreReader = null;
        }
        stopBackgroundThread();
    }

    /**
     * 关闭CameraCaptureSession、CameraDevice对象
     */
    void stopPreview() {
        // stop preview
        try {
            mCameraOpenCloseLock.acquire();
            if (mCaptureSession != null) {
                mCaptureSession.close();
                mCaptureSession = null;
            }
            if (mCameraDevice != null) {
                mCameraDevice.close();
                mCameraDevice = null;
            }
        } catch (InterruptedException e) {
            Log.e(tag, "request access fail !");
            e.printStackTrace();
        } finally {
            mCameraOpenCloseLock.release();
        }
        closeImageReader();
    }

    /**
     * 拍照
     */
    void takePicture() {
        // take picture
        if (mCameraDevice == null) {
            Log.i(tag, "need to start preview before !");
            return;
        }
        try {
            CaptureRequest.Builder mCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            mCaptureRequestBuilder.addTarget(mCapSurface);
            CaptureRequest mCaptureRequest = mCaptureRequestBuilder.build();
            mCaptureSession.capture(mCaptureRequest, new CameraCaptureSession.CaptureCallback() {
                @Override
                public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber) {
                    super.onCaptureStarted(session, request, timestamp, frameNumber);
                    // capture start
                }

                @Override
                public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
                    super.onCaptureCompleted(session, request, result);
                    // capture complete
                }
            }, null);
        } catch (CameraAccessException e) {
            Log.e(tag, "create capture request fail !");
            e.printStackTrace();
        }
    }

    /**
     * 根据CameraId，开启对应摄像头预览
     */
    void startPreview() {
        // start preview
        createImageReader();
        try {
            mCameraOpenCloseLock.acquire();
        } catch (InterruptedException e) {
            Log.e(tag, "request access fail !");
            e.printStackTrace();
        }
        try {
            if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                // TODO: Consider calling
                //    Activity#requestPermissions
                // here to request the missing permissions, and then overriding
                //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
                //                                          int[] grantResults)
                // to handle the case where the user grants the permission. See the documentation
                // for Activity#requestPermissions for more details.
                Log.e(tag, "request permissions fail !");
                return;
            }
            mCameraManager.openCamera(mCameraId, mStateCallback, null);
        } catch (CameraAccessException e) {
            Log.e(tag, "open camera fail !");
            e.printStackTrace();
        }
        Log.d(tag, "camera id set : =3= " + mCameraId);
    }

    /**
     * 确定开启摄像头的编号
     * @param cameraId  摄像头编号
     */
    void setCameraId(String cameraId) {
        // set Camera id
        try {
            for (String id : mCameraManager.getCameraIdList()) {
                if (cameraId.equals(id)) {
                    mCameraId = cameraId;
                    break;
                }
            }
            if (!mCameraId.equals(cameraId)) {
                Log.w(tag, "device does not support the camera id : " + cameraId);
            }
            Log.d(tag, "--> set camera id : " + mCameraId); // default camera id is "0"
        } catch (CameraAccessException e) {
            Log.e(tag, "get camera id list fail !");
            e.printStackTrace();
        }
        // get the params' map for params' setting
        try {
            mStreamConfiguration = mCameraManager.getCameraCharacteristics(mCameraId).get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
        } catch (CameraAccessException e) {
            Log.e(tag, "get camera characteristics fail !");
            e.printStackTrace();
        }
    }

    /**
     * 设置输出照片图像参数
     * @param imageFormat   输出图像格式，暂时只支持YUV_420_888
     * @param imageSize     输出图像大小
     */
    void setCaptureParams(int imageFormat, Size imageSize) {
        // set capture params
        if (mStreamConfiguration == null) {
            Log.i(tag, "need to set camera id before !");
            return;
        }
        for (int num : mStreamConfiguration.getOutputFormats()) {
            if (imageFormat == num) {
                mCapFormat = imageFormat;
                break;
            }
        }
        if (mCapFormat != imageFormat) {
            Log.w(tag, "device dose not support the image format : " + imageFormat);
        }
        Log.d(tag, "--> set capture format : " + mCapFormat);   // default capture format is YV12
        for (Size size : mStreamConfiguration.getOutputSizes(mCapFormat)) {
            if (size.equals(imageSize)) {
                mCapSize = imageSize;
                break;
            }
        }
        if (!mCapSize.equals(imageSize)) {
            Log.w(tag, "device dose not support the image size : " + imageSize.toString());
        }
        Log.d(tag, "--> set capture size : " + mCapSize.toString());    // default capture size is 1920x1080
    }

    /**
     * 设置输出预览图像参数
     * @param imageFormat   输出图像格式，暂时只支持YUV_420_888
     * @param imageSize     输出图像大小
     */
    void setPreviewParams(int imageFormat, Size imageSize) {
        // set preview params
        if (mStreamConfiguration == null) {
            Log.i(tag, "need to set camera id before !");
            return;
        }
        for (int num : mStreamConfiguration.getOutputFormats()) {
            if (imageFormat == num) {
                mPreFormat = imageFormat;
                break;
            }
        }
        if (mPreFormat != imageFormat) {
            Log.w(tag, "device dose not support the image format : " + imageFormat);
        }
        Log.d(tag, "--> set preview format : " + mPreFormat);   // default preview format is YV12
        for (Size size : mStreamConfiguration.getOutputSizes(mPreFormat)) {
            if (size.equals(imageSize)) {
                mPreSize = imageSize;
                break;
            }
        }
        if (!mPreSize.equals(imageSize)) {
            Log.w(tag, "device dose not support the image size : " + imageSize.toString());
        }
        Log.d(tag, "--> set preview size : " + mPreSize.toString());    // default capture size is 1280x720
    }

    /**
     * 查看当前环境下，设备支持的CameraId，以及对应的ImageFormat和ImageSize
     */
    void checkCameraParams() {
        // check camera parameters
        try {
            for (String id : mCameraManager.getCameraIdList()) {
                Log.d(tag, "== Camera Id : " + id + " ==");
                CameraCharacteristics cameraCharacteristics = mCameraManager.getCameraCharacteristics(id);
                StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                assert map != null;
                for (int format : map.getOutputFormats()) {
                    for (Size size : map.getOutputSizes(format)) {
                        Log.d(tag, "format : " + format + " -> size : " + size.toString());
                    }
                }
            }
        } catch (CameraAccessException e) {
            Log.e(tag, "get camera id list fail !");
            e.printStackTrace();
        }
    }

    /**
     * 为拍照和预览添加ImageReader，用于拍照和预览的数据处理
     */
    private void createImageReader() {
        // create capture image reader
        mCapReader = ImageReader.newInstance(mCapSize.getWidth(), mCapSize.getHeight(), mCapFormat, 1);
        mCapReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
            @Override
            public void onImageAvailable(ImageReader imageReader) {
                // for capture
                mBackgroundHandler.post(new CaptureProcess(imageReader.acquireNextImage()));
            }
        }, null);

        // create preview image reader
        mPreReader = ImageReader.newInstance(mPreSize.getWidth(), mPreSize.getHeight(), mPreFormat, 2);
        mPreReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
            @Override
            public void onImageAvailable(ImageReader imageReader) {
                // for preview
                mBackgroundHandler.post(new PreviewProcess(imageReader.acquireNextImage()));
            }
        }, null);
    }

    /**
     * 关闭拍照和预览的ImageReader，避免关闭相机后，还在显示队列里的图像，不便于后续处理
     * 此处函数可以解决摄像头转换后，输出图像与摄像头能够达到同步，而不是图像与摄像头错位问题
     */
    private void closeImageReader() {
        if (mCapReader != null) {
            mCapReader.close();
        }
        if (mPreReader != null) {
            mPreReader.close();
        }
    }

    // for device
    private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice cameraDevice) {
            // create capture session
            mCameraOpenCloseLock.release();
            mCameraDevice = cameraDevice;
            // create capture session
            mCapSurface = mCapReader.getSurface();
            mPreSurface = mPreReader.getSurface();
            try {
                mCameraDevice.createCaptureSession(Arrays.asList(mCapSurface, mPreSurface), mSessionStateCallback, null);
            } catch (CameraAccessException e) {
                Log.e(tag, "create capture session fail !");
                e.printStackTrace();
            }
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice cameraDevice) {
            //
            mCameraOpenCloseLock.release();
            cameraDevice.close();
            cameraDevice = null;
        }

        @Override
        public void onError(@NonNull CameraDevice cameraDevice, int i) {
            //
            mCameraOpenCloseLock.release();
            cameraDevice.close();
            cameraDevice = null;
        }
    };
    // for session
    private CameraCaptureSession.StateCallback mSessionStateCallback = new CameraCaptureSession.StateCallback() {
        @Override
        public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
            // config session
            mCaptureSession = cameraCaptureSession;
            // set repeating request
            try {
                CaptureRequest.Builder mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                mPreviewRequestBuilder.addTarget(mPreSurface);
                CaptureRequest mPreviewRequest = mPreviewRequestBuilder.build();
                mCaptureSession.setRepeatingRequest(mPreviewRequest, new CameraCaptureSession.CaptureCallback() {
                    @Override
                    public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber) {
                        super.onCaptureStarted(session, request, timestamp, frameNumber);
                        // capture start
                    }

                    @Override
                    public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
                        super.onCaptureCompleted(session, request, result);
                        // capture complete
                    }
                }, null);
            } catch (CameraAccessException e) {
                Log.e(tag, "create capture request fail !");
                e.printStackTrace();
            }
        }

        @Override
        public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
            // config session fail
        }
    };

    /**
     * 背景线程的控制函数，HandlerThread，用于处理预览和拍照输出的图像数据
     */
    private void startBackgroundThread() {
        mBackgroundThread = new HandlerThread("CameraBackgroundThread");
        mBackgroundThread.start();
        mBackgroundHandler = new Handler(mBackgroundThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                // deal with message
            }
        };
    }
    private void stopBackgroundThread() {
        mBackgroundThread.quitSafely();
        try {
            mBackgroundThread.join();
            mBackgroundThread = null;
            mBackgroundHandler = null;
        } catch (InterruptedException e) {
            Log.e(tag, "thread join fail !");
            e.printStackTrace();
        }

    }

    /**
     * 背景线程的处理函数，runnable，图像数据处理
     */
    private class CaptureProcess implements Runnable {

        private byte[] bytes;

        CaptureProcess(Image image) {
            // get nv21 from image
            bytes = MTools.imageYUV420ToNV21(image);
            image.close();
        }

        @Override
        public void run() {
            // capture process
            String path = "/sdcard/capture_" + mCapSize.toString() + ".nv21";
            MTools.saveImageByAppend(path, bytes);
            Log.d(tag, "capture process...");
        }
    }
    private class PreviewProcess implements Runnable {

        private byte[] bytes;

        PreviewProcess(Image image) {
            // get nv21 form image
            bytes = MTools.imageYUV420ToNV21(image);
            image.close();
        }

        @Override
        public void run() {
            // preview process
//            String path = "/sdcard/filter_preview_" + mPreSize.toString() + ".nv21";
//            MTools.saveImageByAppend(path, bytes);
            // set render's camera id
            mGLRenderer.setCameraId(mCameraId);
            // set width and height for OpenGL
            mGLRenderer.setWidthAndHeight(mPreSize.getWidth(), mPreSize.getHeight());
            mGLRenderer.updateImage(bytes);
            Log.d(tag, "preview process...");
        }
    }
}
